#pragma once
#include <cmath>
#include <boost/mp11.hpp>
#include <boost/describe/enum.hpp>
#include <boost/describe/enumerators.hpp>
#include <SFML/System/Vector2.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Color.hpp>
#include <iostream>

namespace std
{
template<>
struct hash<sf::Vector2i> {
    size_t operator()(const sf::Vector2i &vec) const
    {
        hash<int> int_hash;
        size_t hq = int_hash(vec.x);
        size_t hr = int_hash(vec.y);
        return hq ^ (hr + 0x9e3779b9 + (hq << 6) + (hq >> 2));
    }
};

}

template <class T>
inline T distance2(T x1, T y1, T x2, T y2)
{
    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}

inline float distance2(const sf::Vector2f &a, const sf::Vector2f &b)
{
    return distance2(a.x, a.y, b.x, b.y);
}

inline float distance(const sf::Vector2f &a, const sf::Vector2f &b)
{
    return sqrt(distance2(a, b));
}

template<class T>
inline T clamp(T value, T low, T high)
{
    if (value < low)
        return low;
    else if (value > high)
        return high;
    return value;
}

inline auto dot(const sf::Vector2f &lhs, const sf::Vector2f &rhs)
{
    return lhs.x * rhs.x + lhs.y * rhs.y;
}

inline auto length2(const sf::Vector2f &vector) noexcept
{
    return dot(vector, vector);
}

inline auto length(const sf::Vector2f &vector) noexcept
{
    return std::sqrt(length2(vector));
}

template<class T>
inline bool isEqual(T a, T b, T t = 1e-6)
{
    return std::fabs(a - b) < t;
}

void scaleVector2f(sf::Vector2f &vec, float value);
void normalizeVector2f(sf::Vector2f &vec);

inline sf::Vector2f getRectCenter(const sf::FloatRect &area)
{
    return sf::Vector2f(area.left + area.width * 0.5f, area.top + area.height * 0.5f);
}

inline float clipAngle(float angle)
{
    if (angle > 360.0)
        return angle - 360;
    else if (angle < 0)
        return 360 + angle;
    return angle;
}

inline bool shouldRandDoIt(int p)
{
    int value = rand() % 100 + 1;
    return p >= value;
}

inline sf::Vector2f interpolate(const sf::Vector2f &a, const sf::Vector2f &b,
                                float p = 0.5f)
{
    p = clamp(p, 0.0f, 1.0f);
    return sf::Vector2f(p * a.x + (1 - p) * b.x, p * a.y + (1 - p) * b.y);
}

inline sf::Color darken(const sf::Color &c, float r)
{
    return sf::Color(c.r * (1.f - r), c.g * (1.f - r), c.b * (1.f - r), c.a);
}

inline sf::Color lighten(const sf::Color &c, float r)
{
    return sf::Color(c.r * (1.f + r), c.g * (1.f + r), c.b * (1.f + r), c.a);
}

inline sf::Color interpolate(const sf::Color &c1, const sf::Color &c2, float r)
{
    return sf::Color(c1.r + (c2.r - c1.r) * r, c1.g + (c2.g - c1.g) * r, c1.b + (c2.b - c1.b) * r,
                     c1.a + (c2.a - c1.a) * r);
}

inline sf::Color complementary(const sf::Color &c)
{
    return sf::Color(255 - c.r, 255 - c.g, 255 - c.b, c.a);
}

sf::FloatRect expandRect(const sf::FloatRect &a, const sf::FloatRect &b);

/*
template <typename T>
std::ostream &operator<<(std::ostream &os, sf::Vector2<T> v)
{
    os << v.x << ", " << v.y;
    return os;
}

template <typename T>
inline std::ostream &operator<<(std::ostream &os, sf::Rect<T> r)
{
    os << r.left << "," << r.top << ";" << r.width << "," << r.height;
    return os;
}
*/
